// ========================================================================
// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at 
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses. 
// ========================================================================

package org.eclipse.jetty.server.session;

import java.security.SecureRandom;
import java.util.Random;

import javax.servlet.http.HttpServletRequest;

import org.eclipse.jetty.server.SessionIdManager;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

public abstract class AbstractSessionIdManager extends AbstractLifeCycle implements SessionIdManager
{

	private static final Logger LOG = Log.getLogger(AbstractSessionIdManager.class);

	private final static String __NEW_SESSION_ID = "org.eclipse.jetty.server.newSessionId";

	protected Random _random;
	protected boolean _weakRandom;
	protected String _workerName;

	/* ------------------------------------------------------------ */
	public AbstractSessionIdManager()
	{}

	/* ------------------------------------------------------------ */
	public AbstractSessionIdManager(Random random)
	{
		_random = random;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Get the workname. If set, the workername is dot appended to the session ID and can be used to assist session affinity in a load balancer.
	 * 
	 * @return String or null
	 */
	public String getWorkerName()
	{
		return _workerName;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Set the workname. If set, the workername is dot appended to the session ID and can be used to assist session affinity in a load balancer.
	 * 
	 * @param workerName
	 */
	public void setWorkerName(String workerName)
	{
		if (workerName.contains("."))
			throw new IllegalArgumentException("Name cannot contain '.'");
		_workerName = workerName;
	}

	/* ------------------------------------------------------------ */
	public Random getRandom()
	{
		return _random;
	}

	/* ------------------------------------------------------------ */
	public void setRandom(Random random)
	{
		_random = random;
		_weakRandom = false;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Create a new session id if necessary.
	 * 
	 * @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long)
	 */
	@SuppressWarnings("null")
	public String newSessionId(HttpServletRequest request, long created)
	{
		synchronized (this)
		{
			if (request != null)
			{
				// A requested session ID can only be used if it is in use already.
				String requested_id = request.getRequestedSessionId();
				if (requested_id != null)
				{
					String cluster_id = getClusterId(requested_id);
					if (idInUse(cluster_id))
						return cluster_id;
				}

				// Else reuse any new session ID already defined for this request.
				String new_id = (String)request.getAttribute(__NEW_SESSION_ID);
				if (new_id != null && idInUse(new_id))
					return new_id;
			}

			// pick a new unique ID!
			String id = null;
			while (id == null || id.length() == 0 || idInUse(id))
			{
				long r0 = _weakRandom
					? (hashCode() ^ Runtime.getRuntime().freeMemory() ^ _random.nextInt() ^ (((long)request.hashCode()) << 32))
					: _random.nextLong();
				if (r0 < 0)
					r0 = -r0;
				long r1 = _weakRandom
					? (hashCode() ^ Runtime.getRuntime().freeMemory() ^ _random.nextInt() ^ (((long)request.hashCode()) << 32))
					: _random.nextLong();
				if (r1 < 0)
					r1 = -r1;
				id = Long.toString(r0, 36) + Long.toString(r1, 36);

				//add in the id of the node to ensure unique id across cluster
				//NOTE this is different to the node suffix which denotes which node the request was received on
				if (_workerName != null)
					id = _workerName + id;
			}

			request.setAttribute(__NEW_SESSION_ID, id);
			return id;
		}
	}

	/* ------------------------------------------------------------ */
	@Override
	protected void doStart() throws Exception
	{
		initRandom();
	}

	/* ------------------------------------------------------------ */
	@Override
	protected void doStop() throws Exception
	{}

	/* ------------------------------------------------------------ */
	/**
	 * Set up a random number generator for the sessionids. By preference, use a SecureRandom but allow to be injected.
	 */
	public void initRandom()
	{
		if (_random == null)
		{
			try
			{
				_random = new SecureRandom();
			} catch (Exception e)
			{
				LOG.warn("Could not generate SecureRandom for session-id randomness", e);
				_random = new Random();
				_weakRandom = true;
			}
		}
		else
			_random.setSeed(_random.nextLong() ^ System.currentTimeMillis() ^ hashCode() ^ Runtime.getRuntime().freeMemory());
	}

}
